Sieci komputerowe — materiały dodatkowe
Temat zajęć: Programowanie gniazd w systemie Windows - biblioteka WinSock32.
Literatura:
- R. Stevens, "Programowanie zastosowań sieciowych w systemie Unix"
- A. Jones, J. Ohlund, "Programowanie sieciowe Microsoft Windows"
- C. Petzold, "Programowanie Windows"
- M. Gabassi, B. Dupouy, "Przetwarzanie rozproszone w systemie Unix"
- E. Harold, "Java: programowanie sieciowe"
- R. Stevens, "Biblia TCP/IP" (tom 1 - Protokoły i tom 2 - Implementacje)
- C. Hunt, "TCP/IP - administracja sieci"
- V. Toth, "Programowanie Windows 98/NT - księga eksperta"
- Dokumenty RFC wersja on-line
- Plik pomocy dla WinSock pobierz
- Dokumentacja WinSock w MSDN wersja on-line
Programowanie gniazd w systemie Windows - wstęp
Gniazda w systemach Windows zaimplementowane są w zewnętrznej bibliotece dynamicznej wsock32.dll. Dlatego Przy kompilacji programów korzystających z WinSock należy pamiętać o linkowaniu ich z biblioteką importową wsock32.lib (ew. libwsock32.a dla kompilatorów zgodnych z GNU). Biblioteka ta nie jest dołączana domyślnie.W przypadku korzystania ze środowiska Visual Studio należy dodać wsock32.lib do zestawu bibliotek dołączanych do kodu (w opcjach projektu):
Fakt, że gniazda sieciowe implementowane są w zewnętrznej bibliotece ma również inne implikacje. Deskryptory gniazd sieciowych nie są w systemach Windows traktowane tak samo, jak pozostałe deskryptory (w przeciwieństwie do systemów Unixowych, gdzie można stosować prawie wszystkie funkcje operujące na deskryptorach również dla gniazd sieciowych). Dlatego m. in. gniazda nie są zamykane standardową funkcją close, lecz specjalną funkcją closesocket. Ponadto typem deskryptorów gniazd jest SOCKET, a nie int.
Biblioteka WinSock oferuje dwa interfejsy programistyczne: jeden niemal całkowicie zgodny z interfejsem gniazd BSD, drugi różniący się od BSD sockets, ale umożliwiający wykorzystanie pewnych specyficznych właściwości gniazd w systemie Windows. Funkcje z pierwszej grupy posiadają nazwy takie, jak ich odpowiedniki z BSD sockets (np. socket, listen, connect), zaś funkcje z drugiej grupy posiadają przedrostek WSA (np. WSASocket, WSAAsyncSelect). Definicje wszystkich funkcji biblioteki WinSock znajdują się w pliku nagłówkowym winsock2.h (dla wersji biblioteki 2.x) lub w winsock.h (dla wcześniejszych wersji).
Inicjalizacja biblioteki
Bibliotekę WinSock należy najpierw zainicjować za pomocą funkcji WSAStartup (funkcję tą należy wywołać przed korzystaniem z jakiejkolwiek innej funkcji biblioteki WinSock). Jako parametry podajemy jej wymaganą przez naszą aplikację minimalną wersję biblioteki oraz wskaźnik na strukturę WSADATA, w której umieszczone zostaną dodatkowe informacje o bibliotece. Do najważniejszych pól struktury WSADATA należą:- WORD wHighVersion - określa maksymalny numer wersji WinSock wspierany przez daną implementację
- char szDescription[] - opis biblioteki w formie łańcucha tekstowego (zwykle nazwa i numer wersji)
- unsigned short iMaxSockets - maksymalna liczba gniazd, jaką może otworzyć jeden proces (0 oznacza brak ograniczeń dla procesu)
- unsigned short iMaxUdpDg - maksymalny rozmiar datagramu UDP (0 oznacza brak ograniczeń)
Przykład 1
Inicjalizacja WinSock (wymagana wersja 2.0) i wyświetlenie informacji z WSADATA.Spakowane solution Visual Studio pobierz
Plik c4p1.cpp pobierz
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
int main(int argc, char **argv)
{
WORD RequiredVersion;
WSADATA WData;
RequiredVersion = MAKEWORD(2, 0);
if (WSAStartup(RequiredVersion, &WData) != 0) {
printf("Blad inicjalizacji WinSock2\n");
return 1;
}
printf("Wersja WinSock: %d.%d\n",
HIBYTE(WData.wHighVersion),
LOBYTE(WData.wHighVersion));
printf("Opis: %s\n", WData.szDescription);
printf("Stan systemu: %s\n", WData.szSystemStatus);
printf("Maksymalna liczba gniazd: %u\n",
WData.iMaxSockets);
printf("Maksymalny rozmiar datagramu UDP: %u\n",
WData.iMaxUdpDg);
WSACleanup();
system("PAUSE");
return 0;
}
Komunikacja TCP w WinSock
Sekwencja wywołań funkcji w przypadku klienta i serwera jest taka, jak w gniazdach BSD. Dla komunikacji TCP po stronie klienta należy wywołać kolejno:- socket
- connect
- send / recv
- closesocket
Sekwencja wywołań w przypadku serwera TCP jest następująca:
- socket
- bind
- listen
- accept (nowe gniazdo)
- recv / send
- closesocket (nowe gniazdo)
- closesocket (gniazdo nasłuchujące)
Przykład 2
Serwer nasłuchuje na podanym porcie na protokole TCP. Klient łączy się na ten port i wysyła pewną liczbę całkowitą. Serwer zwiększa otrzymaną liczbę o 1 i odsyła klientowi, który wyświetla otrzymaną wartość na konsoli. W przypadku obu transmisji liczby przesyłane są w formacie sieci.Spakowane solution Visual Studio pobierz
Plik c4p2a.cpp pobierz
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
int main(int argc, char **argv)
{
WORD RequiredVersion;
WSADATA WData;
SOCKET ss, s;
struct sockaddr_in addr, incoming;
int alen;
long val;
if (argc<2) {
printf("podaj numer portu jako parametr\n");
return 1;
}
RequiredVersion = MAKEWORD(2, 0);
if (WSAStartup(RequiredVersion, &WData) != 0) {
printf("Blad inicjalizacji WinSock2\n");
return 1;
}
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[1]));
addr.sin_addr.s_addr = INADDR_ANY;
ss = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
bind(ss,(struct sockaddr*) &addr, sizeof(addr));
listen(ss, 10);
alen = sizeof(incoming);
while((s = accept(ss, (struct sockaddr*) &incoming, &alen))
!= INVALID_SOCKET) {
recv(s, (char*) &val, sizeof(long), 0);
val = ntohl(val);
printf("Otrzymano %d od %s:%u\n",
val, inet_ntoa(incoming.sin_addr),
ntohs(incoming.sin_port));
if (val == 0) break;
val++;
val = htonl(val);
send(s, (char*) &val, sizeof(long), 0);
closesocket(s);
}
closesocket(ss);
WSACleanup();
return 0;
}
Plik c4p2b.cpp pobierz
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
int main(int argc, char **argv)
{
WORD RequiredVersion;
WSADATA WData;
SOCKET s;
struct sockaddr_in addr;
long val;
struct hostent *he;
if (argc<4) {
printf("podaj nazwe hosta, port i liczbe jako parametry\n");
return 1;
}
RequiredVersion = MAKEWORD(2, 0);
if (WSAStartup(RequiredVersion, &WData) != 0) {
printf("Blad inicjalizacji WinSock2\n");
return 1;
}
he = gethostbyname(argv[1]);
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = *((unsigned long*) he->h_addr);
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
connect(s, (struct sockaddr*) &addr, sizeof(addr));
val = atoi(argv[3]);
printf("Wysylam %d do %s\n",val,inet_ntoa(addr.sin_addr));
val = htonl(val);
send(s, (char*) &val, sizeof(long), 0);
printf("Czekam na odpowiedz...\n");
recv(s, (char*) &val, sizeof(long), 0);
val = ntohl(val);
printf("Odpowiedz: %d\n", val);
closesocket(s);
WSACleanup();
return 0;
}
Zadanie 1
Przeimplementować serwer i klienta z przykładu 2 tak, aby korzystał z protokołu UDP. Należy zwrócić uwagę na:- tworzenie gniazd - podajemy SOCK_DGRAM i IPPROTO_UDP zamiast SOCK_STREAM i IPPROTO_TCP
- odbiór i wysyłanie danych - nie korzystamy z send i recv, lecz z sendto i recvfrom (parametry identyczne jak w ich odpowiednikach BSD)
- po stronie serwera nie korzystamy z listen i accept (podobnie jak w BSD)
Funkcja WSAAsyncSelect
Przyjrzymy się jednej z funkcji z rodziny WSA, mianowicie WSAAsyncSelect. Działa ona nieco podobnie do znanej nam funkcji select, jednak jest ściśle związana z API Windows. Funkcja ta monitoruje podane gniazdo pod kątem wystąpienia pewnego zdarzenia (podobnie jak select), a w przypadku wystąpienia zdarzenia z podanej kategorii wysyła do okna o podanym uchwycie żądany komunikat. Prototyp jest następujący:WSAAsyncSelect(SOCKET s, HWND hwnd, unsigned int msg, long event)gdzie s oznacza monitorowane gniazdo, hwnd jest uchwytem okna, które ma otrzymać komunikat, msg jest komunikatem, który zostanie wysłany do okna hwnd, a event definiuje jakie rodzaje zdarzeń mają powodować wysłanie komunikatu msg i jest to zwykle suma bitowa pewnej kombinacji następujących stałych (podaję tylko te częściej używane): FD_READ, FD_WRITE, FD_ACCEPT, FD_CONNECT, FD_CLOSE. Zatem np.
WSAAsyncSelect(ss, hwndMainWindow, WM_USER+1, FD_ACCEPT)powoduje monitorowanie (bez blokowania aplikacji) gniazda ss pod kątem możliwości wywołania na nim funkcji accept i w przypadku wykrycia przychodzącego połączenia wysłanie komunikatu o numerze WM_USER+1 do okna o uchwycie hwndMainWindow.
Komunikat taki oczywiście powinien być obsłużony w pętli komunikatów danego okna.
Przykład 4
Jako przykład rozważymy sobie prosty program, który odbiera od klientów wiersze tekstu (jeden w jednym połączeniu) i umieszcza ich zawartość na liście w swoim okienku. Klient (wysyłający komunikaty) działa w konsoli.UWAGA: Ta aplikacja, oprócz linkowania z wsock32.lib, wymaga również biblioteki comctl32.lib.
Spakowane solution Visual Studio pobierz
Plik c4p4a.cpp pobierz
#include <stdio.h>
#include <stdlib.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <winsock2.h>
#include <commctrl.h>
#include <string.h>
#include "c4p4ares.h"
#define WM_POLACZENIE (WM_USER + 100)
static BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
SOCKET ss;
char Sender[256];
char SingleLine[1025];
unsigned long CurrStep = 0L;
void ReadLineFromNetwork(HWND hwndDlg)
{
SOCKET s;
struct sockaddr_in incoming;
int alen = sizeof(struct sockaddr_in);
int toread, rd, offset;
HWND listbox;
KillTimer(hwndDlg, 0);
s = accept(ss, (struct sockaddr*) &incoming, &alen);
if (s == INVALID_SOCKET) {
MessageBox(
NULL,
"accept nie powiodl sie (INVALID_SOCKET)",
"c4p1a",
0);
SetTimer(hwndDlg, 0, 100, NULL);
return;
}
memset(SingleLine, 0, 1025);
toread = 0;
while(recv(s, (char*) &toread, 4, 0) <= 0);
toread = ntohl(toread);
offset = 0;
while (toread > 0) {
rd = recv(s, SingleLine+offset, toread, 0);
offset += rd;
toread -= rd;
}
closesocket(s);
sprintf(Sender,"Wiadomosc od %s:",inet_ntoa(incoming.sin_addr));
listbox = GetDlgItem(hwndDlg, IDC_LIST_KOMUNIKATY);
SendMessage(listbox, LB_ADDSTRING, 0, (LPARAM) Sender);
SendMessage(listbox, LB_ADDSTRING, 0, (LPARAM) SingleLine);
UpdateWindow(listbox);
SetTimer(hwndDlg, 0, 100, NULL);
}
int APIENTRY WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASS wc;
unsigned short port;
struct sockaddr_in addr;
WORD RequiredVersion;
WSADATA WD;
port = atoi(lpCmdLine);
if (port == 0) {
MessageBox(NULL,"Należy podać prawidłowy numer portu jako parametr w wierszu poleceń.","p3_s",0);
return 1;
}
RequiredVersion = MAKEWORD(2, 0);
if (WSAStartup(RequiredVersion, &WD) != 0) {
MessageBox(NULL,"Inicjalizacja biblioteki WinSock 2 nie powiodła się.","p3_s",0);
return 1;
}
port = htons(port);
addr.sin_family = AF_INET;
addr.sin_port = port;
addr.sin_addr.s_addr = INADDR_ANY;
ss = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
bind(ss, (struct sockaddr*) &addr, sizeof(addr));
listen(ss, 10);
memset(&wc,0,sizeof(wc));
wc.lpfnWndProc = DefDlgProc;
wc.cbWndExtra = DLGWINDOWEXTRA;
wc.hInstance = hinst;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
wc.lpszClassName = "c4p1a";
RegisterClass(&wc);
InitCommonControls();
DialogBox(hinst, MAKEINTRESOURCE(IDD_MAINDIALOG), NULL, (DLGPROC) DialogFunc);
closesocket(ss);
WSACleanup();
return 0;
}
static int InitializeApp(HWND hDlg,WPARAM wParam, LPARAM lParam)
{
WSAAsyncSelect(ss, hDlg, WM_POLACZENIE, FD_ACCEPT);
SetTimer(hDlg, 0, 100, NULL);
return 1;
}
static BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_INITDIALOG:
InitializeApp(hwndDlg,wParam,lParam);
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDOK:
EndDialog(hwndDlg,1);
return 1;
case IDCANCEL:
EndDialog(hwndDlg,0);
return 1;
}
break;
case WM_CLOSE:
EndDialog(hwndDlg,0);
return TRUE;
case WM_TIMER:
SendMessage(GetDlgItem(hwndDlg, IDC_PROGRESS), PBM_STEPIT, (WPARAM) 0, (LPARAM) 0);
SetTimer(hwndDlg, 0, 150, NULL);
break;
case WM_POLACZENIE:
ReadLineFromNetwork(hwndDlg);
}
return FALSE;
}
Plik c4p4ares.h pobierz
#define IDD_MAINDIALOG 100
#define IDC_LIST_KOMUNIKATY 101
#define IDC_PROGRESS 103
Plik c4p4a.rc pobierz
#include <windows.h>
#include "c4p4ares.h"
IDD_MAINDIALOG DIALOG 7, 20, 313, 206
STYLE DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "SIK420 - przykład WinSock"
FONT 8, "Helv"
BEGIN
DEFPUSHBUTTON "Zamknij", IDOK, 256, 184, 48, 14
LISTBOX IDC_LIST_KOMUNIKATY, 6, 24, 302, 144, WS_VSCROLL | WS_TABSTOP
LTEXT "Odebrane komunikaty:", 102, 8, 8, 296, 12
CONTROL "", IDC_PROGRESS, "msctls_progress32", 0x0 | WS_CLIPSIBLINGS, 6, 172, 146, 11
END
Plik c4p4b.cpp pobierz
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
char buf[1024];
int main(int argc, char **argv)
{
WORD RequiredVersion;
WSADATA WData;
SOCKET s;
int l;
struct sockaddr_in addr;
struct hostent *he;
if (argc<4) {
printf("podaj nazwe hosta, port i \"tekst\" jako parametry\n");
return 1;
}
RequiredVersion = MAKEWORD(2, 0);
if (WSAStartup(RequiredVersion, &WData) != 0) {
printf("Blad inicjalizacji WinSock2\n");
return 1;
}
he = gethostbyname(argv[1]);
if (he == NULL) {
printf("Nieznany host: %s\n", argv[1]);
return 1;
}
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = *((unsigned long*) he->h_addr);
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (connect(s,
(struct sockaddr*) &addr,
sizeof(addr)) < 0) {
printf("Connect nie powiodl sie.\n");
return 1;
}
strcpy(buf,argv[3]);
l = htonl((long) strlen(buf));
send(s, (const char*) &l, 4, 0);
send(s, buf, (int) strlen(buf), 0);
closesocket(s);
printf("Wiadomosc wyslana do %s:%s\n",argv[1],argv[2]);
WSACleanup();
return 0;
}
Zadanie 2
Zaimplementujemy nieco bardziej złożony program wykorzystujący Winsock do komunikacji sieciowej.Będzie to "mini komunikator", który będzie umożliwiał wysyłanie wiadomości tekstowych do innych użytkowników korzystających z takiego samego programu.
Aby nieco sprawę skomplikować założymy, że nie będzie to prosty komunikator "jeden do jeden", lecz każdy z użytkowników będzie mógł założyć swoją listę "przyjaciół" i przesyłać wiadomości do dowolnego przyjaciela z listy.
Założenia są więc następujące:
- każdy użytkownik systemu "pogaduszek" jest identyfikowany przez swój nick (pseudonim)
- każdy użytkownik posiada listę przyjaciół
- przyjaciel identyfikowany jest przez nick, host i numer portu (na którym nasłuchuje jego program)
- przyjaciół można dodawać i usuwać dynamicznie, w trakcie działania programu
- jeśli program otrzyma wiadomość od przyjaciela (czyli użytkownika, którego nick znajduje się na liście przyjaciół), to natychmiast wyświetla go w oknie aplikacji, w przeciwnym przypadku najpierw pyta użytkownika, czy ten chce odebrać wiadomość od danego nadawcy
- port, na którym nasłuchuje program podawany jest przy jego uruchomieniu jako parametr w wierszu poleceń
- program posiada (proste) GUI w postaci jednego okna
- okno umożliwia wykonanie następujących czynności:
- podanie własnego nick'a
- dodanie / usunięcie przyjaciół (dodanie polega na podaniu nick'a, hosta i portu)
- wysłanie wiadomości do dowolnie wskazanego z listy przyjaciela
- wyczyszczenie listy odebranych wiadomości
- dodatkowo w oknie pojawiają się wysłane i odebrane wiadomości, wraz z nick'ami nadawców
- wiadomość przesyłana jest jako dwa wiersze tekstu, poprzedzone długością danego wiersza (liczba 32-bitowa w formacie sieci) - pierwszy wiersz to nick nadawcy, drugi to treść wiadomości
Gotowy skompilowany exe, pokazujący jak program powinien działać, można pobrać.
Spakowane solution Visual Studio ze szkieletem programu pobierz
Podany poniżej kod programu jest niekompletny - wymaga uzupełnienia. Interesuje nas plik c4z2.cpp. Dobra wiadomość jest taka, że cała część związana z obsługą GUI i logiki aplikacji jest już zaimplementowana. Należy doimplementować kilka funkcji, które zajmują się wyłącznie komunikacją sieciową.
Funkcje wymagające implementacji samodzielnej są wyraźnie wyróżnione w kodzie programu (patrz listing poniżej).
W implementowanych funkcjach można (a nawet trzeba) skorzystać z dwóch następujących funkcji (są już zaimplementowane):
- int czy_akceptujesz_polaczenie(char *nick, struct sockaddr_in *adres) - funkcja ta sprawdzi, czy użytkownik o podanym nick'u jest przyjacielem i jeśli tak, zwróci tak; w przeciwnym przypadku zapyta użytkownika czy ten chce odebrać wiadomość i zwróci tak lub nie w zależności od jego decyzji
- void dodaj_komunikat(char *nick, char *komunikat) - funkcja ta dodaje do pola wyświetlającego przebieg rozmowy wiersz postaci nick mówi: komunikat
- int wystartuj_gniazdo(HWND okno_programu, unsigned short port)
Funkcja ta powinna (korzystając ze zmiennej globalnej gniazdo_nasluchujace) utworzyć nowe gniazdo (za pomocą socket), przypisać je do podanego portu port (wypełniając odpowiednio strukturę sockaddr_in i korzystając z funkcji bind) i wykonać listen na tym gnieździe. Następnie powinna ustawić (za pomocą WSAAsyncSelect) monitorowanie gniazda pod kątem wystąpienia zdarzenia FD_ACCEPT (połączenie przychodzące). Jeśli takie zdarzenie nastąpi, do okna o uchwycie okno_programu powinien zostać wysłany komunikat WM_POLACZENIE. Funkcja powinna zwracać tak jeśli wszystkie operacje się powiodły lub nie jeśli choć jedna z nich zwróciła błąd. - void obsluz_polaczenie(void)
Ta funkcja zostanie wywołana automatycznie przez obsługę pętli komunikatów głównego okna jeśli zostanie wykryty komunikat WM_POLACZENIE. Powinna ona:- wykonać accept na gniazdo_nasluchujace
- odczytać długość nick-a (32-bit, format sieci)
- odczytać nick (wiersz tekstu o odczytanej wcześniej długości)
- wywołać funkcję czy_akceptujesz_polaczenie i jeśli zwróci ona nie, to zamknąć gniazdo zwrócone przez accept i wyjść z funkcji
- w przeciwnym wypadku powinna ponownie odczytać długość tekstu-wiadomości (32-bit, format sieci) oraz samą wiadomość, po czym nick i wiadomość przekazać funkcji dodaj_komunikat
- zamknąć gniazdo zwrócone przez accept
- int wyslij_wiadomosc(struct sockaddr_in *odbiorca, char *mojnick, const char *msg)
Funkcja ta powinna stworzyć nowe, "aktywne", gniazdo, wykonać connect na podany adres odbiorca (struktura przekazana jako parametr będzie już w całości wypełniona, łącznie z numerem portu) i wysłać dwa wiersze tekstu: mojnick i msg. Oba wiersze muszą być poprzedzone ich długościami - liczbami 32-bitowymi w formacie sieci. Można skorzystać z c4p4b.cpp jako ściągawki.
Jeszcze jedna uwaga: proszę nic nie zmieniać poniżej wiersza 128, chyba że dana osoba dokładnie wie co robi.
Plik c4z2.cpp pobierz
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <winsock2.h>
#include "c4z2res.h"
/*
Flagi - wartosci zwracane przez funkcje.
tak - akceptacja, sukces
nie - odrzucenie, porazka
Mozna rownie dobrze uzywac wartosci liczbowych
0 i 1, te stale sa tylko dla wygody.
*/
const int tak = 1;
const int nie = 0;
/*
W tej zmiennej znajdzie sie numer portu do nasluchu,
ktory zostal podany jako parametr w wierszu polecen.
*/
unsigned short nrportu;
/*
W tej zmiennej zawsze znajduje się uchwyt
okna programu (na szczescie program ma tylko
jedno glowne okno :) ).
*/
HWND okno;
/*
Komunikat, ktory powinna wyslac funkcja WSAAsyncSelect
jesli wystapi zdarzenie FD_ACCEPT
*/
#define WM_POLACZENIE (WM_USER + 100)
/*
Z tej zmiennej prosze korzystac do tworzenia glownego
gniazda nasluchujacego. Gniazda obslugujace poszczegolne
polaczenia powinny byc zmiennymi lokalnymi w odpowiednich
procedurach.
*/
SOCKET gniazdo_nasluchujace;
/************** FUNKCJE DO WYKORZYSTANIA ******************/
/*
Ta funkcja zwraca tak lub nie w zaleznosci od tego, czy uzytkownik
akceptuje polaczenie od osoby X, czy nie.
Jesli nick X jest na liscie przyjaciol, to uzytkownik nie jest pytany
(funkcja zwraca od razu "tak").
Jesli nick'a X nie ma, to uzytkownik jest pytany czy akceptuje
komunikat od X.
Implementacja pod koniec tego modulu.
*/
int czy_akceptujesz_polaczenie(const char *odkogo, struct sockaddr_in *adr);
/*
Ta funkcja dodaje komunikat do listy komunikatow.
Implementacja pod koniec modulu.
Podajemy nick nadawcy i tresc wiadomosci.
*/
void dodaj_komunikat(const char *odkogo, const char *komunikat);
/************** FUNKCJE DO ZAIMPLEMENTOWANIA ******************/
/*
Ta funkcja powinna tworzyc gniazdo, bindowac je do podanego jako argument
portu, wywolywac listen oraz ustawiac za pomoca WSAAsyncSelect monitorowanie
gniazda pod katem accept.
Jesli zwroci "nie", to aplikacja zostanie przerwana (czyli funkcja
powinna zwracac "nie" jesli wystapil blad np. w socket, bind czy listen).
UWAGA: port zostanie podany jako liczba w formacie hosta.
Jako gniazdo nalezy wykorzystac zmienna globalna gniazdo_nasluchujace.
*/
int wystartuj_gniazdo(HWND okno_programu, unsigned short port)
{
/* 1 */
return tak;
}
/*
Ta funkcja powinna zaakceptowac przychodzace polaczenie, o ile nick nadawcy
znajduje sie na liscie przyjaciol lub o ile uzytkownik wyrazi zgode
na odebranie wiadomosci od danego nadawcy.
Nalezy skorzystac z wyzej zaimplementowanej funkcji czy_akceptujesz_polaczenie(...).
Funkcje accept nalezy wywolac dla globalnego gniazdo_nasluchujace.
Nalezy odczytac najpierw dlugosc wiersza tekstu (32 bity w formacie sieci),
a nastepnie wiersz tekstu o tej dlugosci.
Odczytany wiersz to nick nadawcy, ktory podajemy funkcji
czy_akceptujesz_polaczenie.
Jesli akceptacja nastapi, nalezy ponownie odczytac dlugosc wiersza tekstu
(32 bity w formacie sieci), a nastepnie sam tekst o tej dlugosci - jest
to wiadomosc.
Za pomoca wywolania funkcji dodaj_komunikat(...) nalezy wstawic wiadomosc
do okienka wiadomosci.
Funkcja ta bedzie wywolana przez petle obslugi komunikatow jako reakcja na
komunikat WM_POLACZENIE, ktory to komunikat wstawi do kolejki funkcja
WSAAsyncSelect jesli nastapi zdarzenie typu accept.
Prosze pamietac o doklejeniu '\0' na koncu odczytanych lancuchow
lub o zerowaniu calego bufora (memset) przed odczytem tekstu.
*/
void obsluz_polaczenie(void)
{
/* 2 */
}
/*
Ta funkcja powinna nawiazac polaczenie z hostem o podanym adresie (numer portu
zawarty jest juz w parametrze).
Nastepnie powinna wyslac dwa wiersze tekstu: mojnick i msg.
Kazdy wiersz powinien byc poprzedzony wyslaniem dlugosci tekstu
jako liczby 32-bitowej w formacie sieci.
Funkcja powinna zwracac tak jesli wyslanie sie powiodlo lub nie jesli wystapil
blad.
*/
int wyslij_wiadomosc(struct sockaddr_in *odbiorca, char *mojnick, const char *msg)
{
/* 3 */
return tak;
}
/************************* PONIZEJ PROSZE JUZ NIC NIE ZMIENIAC *************/
BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
int APIENTRY WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASS wc;
INITCOMMONCONTROLSEX cc;
WORD wersja;
WSADATA d;
nrportu = atoi(lpCmdLine);
if (nrportu < 1025 || nrportu > 65535) {
MessageBox(NULL,
"Podano nieprawidłowy numer portu lub nie podano go wcale.",
"MINIGADU",
MB_ICONERROR
);
return 1;
}
wersja = MAKEWORD(2,0);
WSAStartup(wersja, &d);
memset(&wc,0,sizeof(wc));
wc.lpfnWndProc = DefDlgProc;
wc.cbWndExtra = DLGWINDOWEXTRA;
wc.hInstance = hinst;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
wc.lpszClassName = "minigadu";
RegisterClass(&wc);
memset(&cc,0,sizeof(cc));
cc.dwSize = sizeof(cc);
cc.dwICC = 0xffffffff;
InitCommonControlsEx(&cc);
DialogBox(hinst, MAKEINTRESOURCE(IDD_MAINDIALOG), NULL, (DLGPROC) DialogFunc);
WSACleanup();
return 0;
}
int InitializeApp(HWND hDlg,WPARAM wParam, LPARAM lParam)
{
okno = hDlg;
return wystartuj_gniazdo(hDlg, nrportu);
}
/*
Deklaracja procedur, ktore sa zaimplementowane po petli
komunikatow, a sa w niej wywolywane.
*/
void wyczysc_clicked(void);
void wyslij_clicked(void);
void dodajp_clicked(void);
void usunp_clicked(void);
/*
Obsluga petli komunikatow okna.
*/
BOOL CALLBACK DialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_INITDIALOG:
if (InitializeApp(hwndDlg,wParam,lParam) == 0) exit(1);
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDC_WYCZYSC:
wyczysc_clicked();
break;
case IDC_DODAJP:
dodajp_clicked();
break;
case IDC_USUNP:
usunp_clicked();
break;
case IDC_WYSLIJ:
wyslij_clicked();
break;
}
break;
case WM_POLACZENIE:
obsluz_polaczenie();
break;
case WM_CLOSE:
EndDialog(hwndDlg,0);
return TRUE;
}
return FALSE;
}
int liczba_przyjaciol(void)
{
return (int) SendMessage(
GetDlgItem(okno,IDC_PRZYJACIELE),
LB_GETCOUNT,
(WPARAM) 0,
(LPARAM) 0
);
}
int czy_jest_nick_na_liscie(const char *nick)
{
int i;
char buf[256];
char tmpnick[256];
char tmphost[256];
int tmpport;
for (i = 0; i < liczba_przyjaciol(); i++) {
SendMessage(
GetDlgItem(okno, IDC_PRZYJACIELE),
LB_GETTEXT,
(WPARAM) i,
(LPARAM) buf
);
sscanf(buf,"%s",tmpnick);
sscanf(buf+strlen(tmpnick)+3,"%s",tmphost);
sscanf(buf+strlen(tmpnick)+strlen(tmphost)+6,"%d",&tmpport);
if (! strcmp(tmpnick,nick)) return tak;
}
return nie;
}
void dodaj_przyjaciela(const char *nick, struct sockaddr_in *adr)
{
char przyjaciel[512];
sprintf(przyjaciel,"%s : %s : %u",
nick,
inet_ntoa(adr->sin_addr),
ntohs(adr->sin_port)
);
SendMessage(
GetDlgItem(okno,IDC_PRZYJACIELE),
LB_ADDSTRING,
(WPARAM) 0,
(LPARAM) przyjaciel
);
}
void usun_przyjaciela(void)
{
int ktory;
ktory = (int) SendMessage(
GetDlgItem(okno, IDC_PRZYJACIELE),
LB_GETCURSEL,
(WPARAM) 0,
(LPARAM) 0
);
if (ktory < 0) {
MessageBox(
NULL,
"Należy najpierw wskazać na liście przyjaciela do usunięcia.",
"MINIGADU",
MB_OK|MB_ICONSTOP
);
return;
}
SendMessage(
GetDlgItem(okno, IDC_PRZYJACIELE),
LB_DELETESTRING,
(WPARAM) ktory,
(LPARAM) 0
);
}
int czy_akceptujesz_polaczenie(const char *odkogo, struct sockaddr_in *adr)
{
int jest;
int decyzja;
char buf[512];
jest = czy_jest_nick_na_liscie(odkogo);
if (jest) return tak;
sprintf(buf,
"Nadawcy %s nie ma wśród przyjaciół. Czy chcesz przyjąć wiadomość?",
odkogo
);
decyzja = MessageBox(
NULL,
buf,
"MINIGADU",
MB_YESNO|MB_ICONQUESTION
);
if (decyzja == IDNO) return nie;
else return tak;
}
void wyczysc_clicked(void)
{
SendMessage(
GetDlgItem(okno, IDC_WIADOMOSCI),
LB_RESETCONTENT,
(WPARAM) 0,
(LPARAM) 0
);
}
void usunp_clicked(void)
{
if (liczba_przyjaciol() < 1)
MessageBox(
NULL,
"Twoja lista przyjaciół jest pusta.",
"MINIGADU",
MB_ICONERROR
);
else usun_przyjaciela();
}
void dodajp_clicked(void)
{
char nick[256];
char host[256];
char port[256];
char buf[512];
int p;
struct hostent *he;
struct sockaddr_in adr;
GetDlgItemText(okno, IDC_NICKP, nick, 256);
GetDlgItemText(okno, IDC_HOSTP, host, 256);
GetDlgItemText(okno, IDC_PORTP, port, 256);
if (strlen(nick)<1) {
MessageBox(NULL, "Nie podano nick'a przyjaciela.",
"MINIGADU", MB_ICONERROR);
return;
}
p = atoi(port);
if (p < 1025 || p > 65535) {
MessageBox(NULL, "Numer portu jest nieprawidłowy.",
"MINIGADU", MB_ICONERROR);
return;
}
he = gethostbyname(host);
if (he == NULL) {
sprintf(buf,
"Nazwa/adres hosta %s jest nieprawidłowa(y).",
host);
MessageBox(NULL, buf, "MINIGADU", MB_ICONERROR);
return;
}
adr.sin_port = htons(p);
adr.sin_addr = *((struct in_addr*) he->h_addr);
if (czy_jest_nick_na_liscie(nick)) {
sprintf(buf,
"Przyjaciel o nick'u %s już znajduje się na Twojej liście.",
nick);
MessageBox(NULL, buf, "MINIGADU", MB_ICONERROR);
return;
}
dodaj_przyjaciela(nick, &adr);
}
void wyslij_clicked(void)
{
char mojnick[256];
char wiadomosc[512];
struct sockaddr_in adr;
int wybranyp;
char przyjaciel[512];
char nickp[128];
char hostp[128];
int portp;
int wynik;
GetDlgItemText(okno, IDC_MOJNICK, mojnick, 256);
mojnick[255] = '\0';
if (strlen(mojnick)<1) {
MessageBox(NULL, "Musisz wprowadzić swój nick.",
"MINIGADU", MB_ICONERROR);
return;
}
GetDlgItemText(okno, IDC_WIADOMOSC, wiadomosc, 512);
wiadomosc[511] = '\0';
if (strlen(wiadomosc) < 1) {
MessageBox(NULL, "Nie możesz wysłać pustej wiadomości.",
"MINIGADU", MB_ICONERROR);
return;
}
if (liczba_przyjaciol() < 1) {
MessageBox(NULL, "Musisz mieć co najmniej jednego przyjaciela.",
"MINIGADU", MB_ICONERROR);
return;
}
wybranyp = (int) SendMessage(
GetDlgItem(okno, IDC_PRZYJACIELE),
LB_GETCURSEL,
(WPARAM) 0,
(LPARAM) 0
);
if (wybranyp < 0) {
MessageBox(NULL,
"Wybierz przyjaciela, do którego chcesz wysłać wiadomość.",
"MINIGADU", MB_ICONERROR);
return;
}
SendMessage(
GetDlgItem(okno, IDC_PRZYJACIELE),
LB_GETTEXT,
(WPARAM) wybranyp,
(LPARAM) przyjaciel
);
sscanf(przyjaciel,"%s",nickp);
sscanf(przyjaciel+strlen(nickp)+3,"%s",hostp);
sscanf(przyjaciel+strlen(nickp)+strlen(hostp)+6,"%d",&portp);
adr.sin_family = AF_INET;
adr.sin_port = htons(portp);
adr.sin_addr.s_addr = inet_addr(hostp);
wynik = wyslij_wiadomosc(&adr, mojnick, wiadomosc);
if (wynik == nie) {
MessageBox(NULL,
"Wysłanie wiadomości nie powiodło się.",
"MINIGADU",
MB_ICONERROR
);
}
dodaj_komunikat(mojnick, wiadomosc);
}
void dodaj_komunikat(const char *odkogo, const char *komunikat)
{
char buf[512];
sprintf(buf,"%s mowi: %s",odkogo,komunikat);
SendMessage(
GetDlgItem(okno, IDC_WIADOMOSCI),
LB_ADDSTRING,
(WPARAM) 0,
(LPARAM) buf
);
}
Plik c4z2res.h pobierz
#define IDD_MAINDIALOG 100
#define IDC_MOJNICK 102
#define IDC_WIADOMOSCI 104
#define IDC_WYCZYSC 105
#define IDC_PRZYJACIELE 107
#define IDC_NICKP 109
#define IDC_HOSTP 111
#define IDC_PORTP 113
#define IDC_DODAJP 114
#define IDC_USUNP 115
#define IDC_WIADOMOSC 116
#define IDC_WYSLIJ 117
Plik resource.h pobierz
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by c4z2.rc
//
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC 1
#define _APS_NEXT_RESOURCE_VALUE 101
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
Plik c4z2.rc pobierz
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#define APSTUDIO_HIDDEN_SYMBOLS
#include "windows.h"
#undef APSTUDIO_HIDDEN_SYMBOLS
#include "c4z2res.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// Polish resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_PLK)
#ifdef _WIN32
LANGUAGE LANG_POLISH, SUBLANG_DEFAULT
#pragma code_page(1250)
#endif //_WIN32
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//
IDD_MAINDIALOG DIALOGEX 84, 20, 389, 232
STYLE DS_SETFONT | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION |
WS_SYSMENU
CAPTION "MINIGADU"
FONT 10, "Helv", 0, 0, 0x0
BEGIN
LTEXT "Mój nick:",101,8,8,32,12
EDITTEXT IDC_MOJNICK,40,6,64,12,ES_AUTOHSCROLL
LTEXT "Wiadomości:",103,8,28,44,12
LISTBOX IDC_WIADOMOSCI,8,44,212,148,WS_VSCROLL | WS_TABSTOP
PUSHBUTTON "Wyczyść",IDC_WYCZYSC,180,192,40,14
LTEXT "Przyjaciele:",106,232,6,68,12
LISTBOX IDC_PRZYJACIELE,232,20,148,132,WS_VSCROLL | WS_TABSTOP
LTEXT "Nick:",108,232,180,36,12
EDITTEXT IDC_NICKP,272,176,68,12,ES_AUTOHSCROLL
LTEXT "Host:",110,232,196,20,12
EDITTEXT IDC_HOSTP,272,192,68,12,ES_AUTOHSCROLL
LTEXT "Port:",112,232,212,20,12
EDITTEXT IDC_PORTP,272,208,48,12,ES_RIGHT | ES_AUTOHSCROLL |
ES_NUMBER
PUSHBUTTON "&Dodaj",IDC_DODAJP,340,208,40,14
PUSHBUTTON "&Usuń",IDC_USUNP,340,156,40,14
EDITTEXT IDC_WIADOMOSC,8,212,168,12,ES_AUTOHSCROLL
PUSHBUTTON "&Wyślij",IDC_WYSLIJ,180,212,40,14
END
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#define APSTUDIO_HIDDEN_SYMBOLS\r\n"
"#include ""windows.h""\r\n"
"#undef APSTUDIO_HIDDEN_SYMBOLS\r\n"
"#include ""c4p2res.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
#endif // Polish resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
Proszę spróbować wykonać to zadanie samodzielnie. Jeśli ktoś czuje, że problem go przerasta, można zajrzeć do ściągawki - jest to moja przykładowa implementacja brakujących funkcji.
Zadanie 3
Jest to zadanie dla osób, które nie boją się eksperymentowania z API Win32.Proszę spróbować przerobić program (wykonany wcześniej) z zadania 2 tak, aby wiadomość wysyłana była do wszystkich przyjaciół z listy, a nie tylko do zaznaczonego. W zasadzie część komunikacyjna może pozostać niezmieniona - wiadomość będzie wysyłana i tak do jednego odbiorcy na raz. Należy więc w odpowiedniej pętli wywoływać już zaimplementowaną funkcję wyslij_wiadomosc. Przeróbki wymaga funkcja wyslij_clicked. W tej chwili sprawdza ona, który element listy przyjaciół jest zaznaczony, pobiera ten element, "łamie" go aby uzyskać nick, adres i numer portu, po czym wywołuje wyslij_wiadomosc. Aby zminimalizować ilość kodu, który trzeba zmienić można np. w pętli po kolei zaznaczać elementy listy, po czym korzystać z istniejącego kodu działającego dla jednego zaznaczonego elementu.
Liczbę lementów w kontrolce Listbox można pobrać wysyłając do niej komunikat LB_GETCOUNT (z wParam i lParam równymi 0).
Zaznaczenie elementu o danym numerze (elementy numerowane są od 0) można uzyskać przez wysłanie do kontrolki komunikatu LB_SETCURSEL, gdzie w wParam podajemy numer elementu, a lParam powinien być ustawiony na 0.
Zatem pobranie liczby przyjaciół można zrealizować np. tak:
liczbap = (int) SendMessage(GetDlgItem(okno, IDC_PRZYJACIELE), LB_GETCOUNT, (WPARAM) 0, (LPARAM) 0);a zaznaczenie przyjaciela o numerze p np. tak:
SendMessage(GetDlgItem(okno, IDC_PRZYJACIELE), LB_SETCURSEL, (WPARAM) p, (LPARAM) 0);